package cn.demoncat.util.redis.util;

import cn.demoncat.util.lang.DoubleUtil;
import cn.demoncat.util.lang.LongUtil;
import cn.demoncat.util.mybatis.util.TransactionUtil;
import cn.demoncat.util.redis.config.RedisIsolator;
import cn.demoncat.util.redis.entity.RedisLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.function.Supplier;

/**
 * Redis 数值处理工具
 * 
 * @author 延晓磊
 * @since 2021年02月13日
 */
@Component
public class RedisNumUtil {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedissonUtil redissonUtil;
    @Resource
    private RedisIsolator redisIsolator;

    /**
     * 递增整数型缓存：+1
     *
     * @param key 	Key，如果Key不存在，将设置为0再incr 1，即返回1
     *
     * @return 返回当前数值
     */
    public Long incr(String key) {
        return incr(key, 1);
    }

    /**
     * 递增整数型缓存：+step
     *
     * @param key 	Key，如果Key不存在，将设置为0再incr step，即返回step
     * @param step 	步长
     *
     * @return 返回当前数值
     */
    public Long incr(String key, long step) {
        return str(key).increment(step);
    }

    /**
     * 递增整数型缓存：+1
     *
     * 注意：本方法没有加锁，并发场景不安全
     *
     * @param key 	Key，如果Key不存在（结果=步长）就设值为fn.get()
     * @param fn	默认值
     *
     * @return 返回当前数值
     */
    public Long incr(String key, Supplier<Long> fn) {
        return incr(key, 1, fn);
    }

    /**
     * 递增整数型缓存：+step
     *
     * 注意：本方法没有加锁，并发场景不安全
     *
     * @param key 	Key，如果Key不存在（结果=步长）就设值为fn.get()
     * @param step 	步长
     * @param fn	默认值
     *
     * @return 返回当前数值
     */
    public Long incr(String key, long step, Supplier<Long> fn) {
        // 递增
        long r = incr(key,step);
        // 结果 == 步长 = KEY不存在，设置为指定值
        if (r == step) {
            r = Long.max(LongUtil.toEmpty(fn.get()), step);
            str(key).set(String.valueOf(r));
        }
        return r;
    }

    /**
     * 递增整数型缓存：+1
     *
     * @param key 	Key，如果Key不存在（结果=步长）就设值为fn.get()
     * @param lock	锁（key不存在时加锁，防止重复设默认值）
     * @param fn	初始化器（缓存丢失时容错）
     *
     * @return 返回当前数值
     */
    public Long incr(String key, RedisLock lock, Supplier<Long> fn) {
        return incr(key, 1, lock, fn);
    }

    /**
     * 递增整数型缓存：+step
     *
     * @param key 	Key，如果Key不存在（结果=步长）就设值为fn.get()
     * @param step 	步长
     * @param lock	锁（key不存在时加锁，防止重复设置初始值）
     * @param fn	初始化器（缓存丢失时容错）
     *
     * @return 返回当前数值
     */
    public Long incr(String key, long step, RedisLock lock, Supplier<Long> fn) {
        // 递增
        long r = incr(key,step);
        // 结果 == 步长，表示KEY不存在（首次递增，结果为step），通过初始化器确认/补偿，实现缓存容错
        if (r == step) {
            // 加锁
            r = redissonUtil.lock(lock, () ->{
                // 重新查询
                BoundValueOperations<String, String> operations = str(key);
                long curr = LongUtil.valueOf(operations.get());
                if (curr == step) {
                    // 其它线程未设置，设置初始值
                    curr = Long.max(LongUtil.toEmpty(fn.get()), step);
                    // 更新缓存
                    operations.set(String.valueOf(curr));
                }else{
                    // 其它线程已设置，直接递增
                    curr = incr(key, step);
                }
                return curr;
            });
        }
        return r;
    }

    /**
     * 递增整数型缓存 - 事务提交后：+1
     *
     * @param key 	Key，如果Key不存在，将设置为0再incr 1，即返回1
     */
    public void txIncr(String key) {
        TransactionUtil.afterCommit(() -> incr(key));
    }

    /**
     * 递增整数型缓存 - 事务提交后：+step
     *
     * @param key 	Key，如果Key不存在，将设置为0再incr step，即返回step
     * @param step 	步长
     */
    public void txIncr(String key, long step) {
        TransactionUtil.afterCommit(() -> incr(key,step));
    }

    /**
     * 递增整数型缓存 - 事务提交后：+1
     *
     * 注意：本方法没有加锁，并发场景不安全
     *
     * @param key 	Key，如果Key不存在（结果=步长）就设值为fn.get()
     * @param fn	默认值
     */
    public void txIncr(String key, Supplier<Long> fn) {
        TransactionUtil.afterCommit(() -> incr(key,fn));
    }

    /**
     * 递增整数型缓存 - 事务提交后：+step
     *
     * 注意：本方法没有加锁，并发场景不安全
     *
     * @param key 	Key，如果Key不存在（结果=步长）就设值为fn.get()
     * @param step 	步长
     * @param fn	默认值
     */
    public void txIncr(String key, long step, Supplier<Long> fn) {
        TransactionUtil.afterCommit(() -> incr(key,step,fn));
    }

    /**
     * 递增整数型缓存 - 事务提交后：+1
     *
     * @param key 	Key，如果Key不存在（结果=步长）就设值为fn.get()
     * @param lock	锁（key不存在时加锁，防止重复设默认值）
     * @param fn	初始化器（缓存丢失时容错）
     */
    public void txIncr(String key, RedisLock lock, Supplier<Long> fn) {
        TransactionUtil.afterCommit(() -> incr(key,lock,fn));
    }

    /**
     * 递增整数型缓存 - 事务提交后：+step
     *
     * @param key 	Key，如果Key不存在（结果=步长）就设值为fn.get()
     * @param step 	步长
     * @param lock	锁（key不存在时加锁，防止重复设默认值）                
     * @param fn	默认值
     */
    public void txIncr(String key, long step, RedisLock lock, Supplier<Long> fn) {
        TransactionUtil.afterCommit(() -> incr(key,step,lock,fn));
    }

    /**
     * 递减整数型缓存：-1
     *
     * @param key 	Key，如果Key不存在，将设置为0再decr 1，即返回-1
     *
     * @return 返回当前数值
     */
    public Long decr(String key) {
        return decr(key, 1);
    }

    /**
     * 递减整数型缓存：-step
     *
     * @param key 	Key，如果Key不存在，将设置为0再decr step，即返回-step
     * @param step 	步长
     *
     * @return 返回当前数值
     */
    public Long decr(String key, long step) {
        return str(key).decrement(step);
    }

    /**
     * 递减整数型缓存：-1
     *
     * 注意：本方法没有加锁，并发场景不安全
     *
     * @param key 	Key，如果Key不存在（结果=-步长）就设值为fn.get()
     * @param fn	默认值
     *
     * @return 返回当前数值
     */
    public Long decr(String key, Supplier<Long> fn) {
        return decr(key, 1, fn);
    }

    /**
     * 递减整数型缓存：-step
     *
     * 注意：本方法没有加锁，并发场景不安全
     *
     * @param key 	Key，如果Key不存在（结果=-步长）就设值为fn.get()
     * @param step 	步长
     * @param fn	默认值
     *
     * @return 返回当前数值
     */
    public Long decr(String key, long step, Supplier<Long> fn) {
        // 递增
        long r = decr(key,step);
        // 结果 == -步长 = KEY不存在，设置为指定值
        if (r == -step) {
            r = LongUtil.toEmpty(fn.get());
            str(key).set(String.valueOf(r));
        }
        return r;
    }

    /**
     * 递减整数型缓存 - 事务提交后：-1
     *
     * @param key 	Key，如果Key不存在，将设置为0再decr 1，即返回-1
     */
    public void txDecr(String key) {
        TransactionUtil.afterCommit(() -> decr(key));
    }

    /**
     * 递减整数型缓存 - 事务提交后：-step
     *
     * @param key 	Key，如果Key不存在，将设置为0再decr step，即返回-step
     * @param step 	步长
     */
    public void txDecr(String key, long step) {
        TransactionUtil.afterCommit(() -> decr(key,step));
    }

    /**
     * 递减整数型缓存- 事务提交后：-1
     *
     * 注意：本方法没有加锁，并发场景不安全
     *
     * @param key 	Key，如果Key不存在（结果=-步长）就设值为fn.get()
     * @param fn	默认值
     */
    public void txDecr(String key, Supplier<Long> fn) {
        TransactionUtil.afterCommit(() -> decr(key,fn));
    }

    /**
     * 递减整数型缓存 - 事务提交后：-step
     *
     * 注意：本方法没有加锁，并发场景不安全
     *
     * @param key 	Key，如果Key不存在（结果=-步长）就设值为fn.get()
     * @param step 	步长
     * @param fn	默认值
     */
    public void txDecr(String key, long step, Supplier<Long> fn) {
        TransactionUtil.afterCommit(() -> decr(key,step,fn));
    }

    /**
     * 递增浮点数型缓存
     *
     * @param key 	Key，如果Key不存在，将设置为0再incr，即返回step
     * @param step 	步长
     * @return 返回当前数值
     */
    public Double incr(String key, double step) {
        return str(key).increment(step);
    }

    /**
     * 递增浮点数型缓存
     *
     * 注意：本方法没有加锁，并发场景不安全
     *
     * @param key 	Key，如果Key不存在（结果=步长）就设值为fn.get()
     * @param step 	步长
     * @param fn	默认值
     *
     * @return 返回当前数值
     */
    public Double incr(String key, double step, Supplier<Double> fn) {
        // 递增
        Double r = incr(key,step);
        // 结果 = 步长 == KEY不存在，设置为指定值
        if (r == step) {
            r = Double.max(DoubleUtil.toEmpty(fn.get()), step);
            str(key).set(String.valueOf(r));
        }
        return r;
    }

    /**
     * 递增浮点数型缓存
     *
     * @param key 	Key，如果Key不存在（结果=步长）就设值为fn.get()
     * @param step 	步长
     * @param lock	锁（key不存在时加锁，防止重复设默认值）                
     * @param fn	默认值
     *
     * @return 返回当前数值
     */
    public Double incr(String key, double step, RedisLock lock, Supplier<Double> fn) {
        // 递增
        Double r = incr(key,step);
        // 结果 == 步长 = KEY不存在，设置为指定值
        if (r == step) {
            // 加锁
            r = redissonUtil.lock(lock, () ->{
                // 重新查询
                String s = str(key).get();
                Double l = DoubleUtil.valueOf(s);
                if (l == step) {
                    // 其它线程未设置，设置默认值
                    l = Double.max(DoubleUtil.toEmpty(fn.get()), step);
                    // 更新缓存
                    str(key).set(String.valueOf(l));
                }else{
                    // 其它线程已设置，直接递增
                    l = incr(key, step);
                }
                return l;
            });
        }
        return r;
    }

    /**
     * 递增整数型缓存 - 事务提交后：+step
     *
     * @param key 	Key，如果Key不存在，将设置为0再incr step，即返回step
     * @param step 	步长
     */
    public void txIncr(String key, double step) {
        TransactionUtil.afterCommit(() -> incr(key,step));
    }

    /**
     * 递增整数型缓存 - 事务提交后：+step
     *
     * 注意：本方法没有加锁，并发场景不安全
     *
     * @param key 	Key，如果Key不存在（结果=步长）就设值为fn.get()
     * @param step 	步长
     * @param fn	默认值
     */
    public void txIncr(String key, double step, Supplier<Double> fn) {
        TransactionUtil.afterCommit(() -> incr(key,step,fn));
    }

    /**
     * 递增整数型缓存 - 事务提交后：+step
     *
     * @param key 	Key，如果Key不存在（结果=步长）就设值为fn.get()
     * @param step 	步长
     * @param lock	锁（key不存在时加锁，防止重复设默认值）
     * @param fn	默认值
     */
    public void txIncr(String key, double step, RedisLock lock, Supplier<Double> fn) {
        TransactionUtil.afterCommit(() -> incr(key,step,lock,fn));
    }

    /**
     * 递减浮点数型缓存
     *
     * @param key 	Key，如果Key不存在，将设置为0再decr，即返回-step
     * @param step 	步长
     * @return 返回当前数值
     */
    public Double decr(String key, double step) {
        return str(key).increment(-step);
    }

    /**
     * 递减浮点数型缓存：-step
     *
     * @param key 	Key，如果Key不存在（结果=-步长）就设值为fn.get()
     * @param step 	步长
     * @param fn	默认值
     *
     * @return 返回当前数值
     */
    public Double decr(String key, double step, Supplier<Double> fn) {
        // 递增
        Double r = decr(key,step);
        // 结果 = -步长 == KEY不存在，设置为指定值
        if (r == -step) {
            r = Double.min(DoubleUtil.toEmpty(fn.get()), -step);
            str(key).set(String.valueOf(r));
        }
        return r;
    }

    /**
     * 递减浮点数型缓存 - 事务提交后：-step
     *
     * @param key 	Key，如果Key不存在，将设置为0再decr step，即返回-step
     * @param step 	步长
     */
    public void txDecr(String key, double step) {
        TransactionUtil.afterCommit(() -> decr(key,step));
    }

    /**
     * 递减浮点数型缓存 - 事务提交后：-step
     *
     * 注意：本方法没有加锁，并发场景不安全
     *
     * @param key 	Key，如果Key不存在（结果=-步长）就设值为fn.get()
     * @param step 	步长
     * @param fn	默认值
     */
    public void txDecr(String key, double step, Supplier<Double> fn) {
        TransactionUtil.afterCommit(() -> decr(key,step,fn));
    }

    /**
     * Redis模板
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年04月01日
     */
    public StringRedisTemplate getRedisTemplate() {
        return stringRedisTemplate;
    }

    /**
     * 获取Redis String
     *
     * @param key
     * @return
     *
     * @author 延晓磊
     * @since 2021年02月10日
     */
    private BoundValueOperations<String, String> str(String key){
        return stringRedisTemplate.boundValueOps(redisIsolator.getKey(key));
    }
}
